/*
Package gfx implements a backend for graphics. The backend connects to various
drawing engines (e.g., raster, SVG, PDF, ...).
I have migrated to the Canvas-projekt (https://github.com/tdewolff/canvas)
for the backend. It is a Cairo-style library and is becoming more complete
by the minute.

That said, I am not convinced every use case of TySE will require
(or justify to include) a large library like Surface. I therefore
try do de-couple it (at compile time) for less sophisticated use cases.
I will retain interoperatibility to Surface, however, to allow users
mix in Surface code for more demanding pieces of art.

We abstract the details away with an interface 'Surface'
(used to be named 'Canvas', but I dropped the name to avoid confusion)
which represents
a rectangular drawing area. The main operation on a Surface is AddContour,
the drawing and/or filling of a path.

Clients often won't use Surface directly, but rather an enclosing struct type
Picture, which holds a Surface plus some administrative information.


BSD License

Copyright (c) 2017-21, Norbert Pillmayer <norbert@pillmayer.com>

All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:

1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.

3. Neither the name of Norbert Pillmayer nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

*/
package gfx

import (
	"image/color"

	"github.com/npillmayer/schuko/gtrace"

	"github.com/npillmayer/arithm"
	"github.com/npillmayer/schuko/tracing"
)

// G traces to the graphics tracer.
func G() tracing.Trace {
	return gtrace.GraphicsTracer
}

// Surface is an interface type for a drawing surface.
type Surface interface {
	W() float64                                                    // width
	H() float64                                                    // height
	AddContour(DrawableContour, float64, color.Color, color.Color) // filldraw with pen with colors
	SetOption(int)                                                 // set a drawing option
}

// debuggingSurface is the default surface for Pictures.
// It will simply collected and remember all strokes.
//
// Shipout will be with a DebuggingOutputRoutine.
type debuggingSurface struct {
	level      tracing.TraceLevel
	strokes    []DrawableContour
	minx, maxx float64
	miny, maxy float64
}

// NewDebuggingSurface creates and returns a pseudo-Surface usable for
// tests and debugging.
func NewDebuggingSurface() Surface {
	s := &debuggingSurface{
		level:   tracing.LevelDebug,
		strokes: make([]DrawableContour, 0, 20),
	}
	return s
}

func (s *debuggingSurface) W() float64 {
	return s.maxx - s.minx
}
func (s *debuggingSurface) H() float64 {
	return s.maxy - s.miny
}

// AddContour is MetaFont's "filldraw with pen with colors".
func (s *debuggingSurface) AddContour(cntr DrawableContour, pen float64, fg color.Color, bg color.Color) {
	s.strokes = append(s.strokes, cntr)
}

// SetOption sets a drawing option
func (s *debuggingSurface) SetOption(int) {
}

// --- Picture ---------------------------------------------------------------

// Picture is the type for the backend to work with.
type Picture struct {
	Name         string
	surface      Surface
	currentColor color.Color
	currentPen   *Pen
	output       OutputRoutine
}

// Pen is a drawing pen.
type Pen struct {
	diameter float64
	style    int
}

// PENCIRCLE is a pen with a round nib
const PENCIRCLE int = 1

// PENSQUARE is a pen with a square nib
const PENSQUARE int = 2

// NewPicture creates a
// new Picture. Caller has to provide an identifier
// and a Surface.
func NewPicture(name string, surface Surface) *Picture {
	pic := &Picture{
		Name:         name,
		surface:      surface,
		currentColor: color.Black,
		currentPen:   NewPencircle(1.0),
	}
	return pic
}

// NewPencircle creates a new round pen.
func NewPencircle(diam float64) *Pen {
	pen := &Pen{
		diameter: diam,
		style:    PENCIRCLE,
	}
	return pen
}

// NewPensquare create a new square pen.
func NewPensquare(diam float64) *Pen {
	pen := &Pen{
		diameter: diam,
		style:    PENSQUARE,
	}
	return pen
}

// Draw a line. Uses the current pen.
func (pic *Picture) Draw(contour DrawableContour) {
	pendiam := pic.currentPen.diameter
	pic.surface.SetOption(pic.currentPen.style)
	pic.surface.AddContour(contour, pendiam, pic.currentColor, nil)
}

// Fill a closed path. Uses the current pen.
func (pic *Picture) Fill(contour DrawableContour) {
	pendiam := pic.currentPen.diameter
	pic.surface.AddContour(contour, pendiam, nil, pic.currentColor)
}

// FillDraw is
// MetaPost's filldraw command. Uses the current pen and color.
func (pic *Picture) FillDraw(contour DrawableContour) {
	pendiam := pic.currentPen.diameter
	pic.surface.SetOption(pic.currentPen.style)
	pic.surface.AddContour(contour, pendiam, pic.currentColor, pic.currentColor)
}

// SetPen sets
// the current pen. Will be used for subsequent drawing operations.
func (pic *Picture) SetPen(pen *Pen) {
	pic.currentPen = pen
}

// SetColor sets
// the current color. Will be used for subsequent drawing operations.
func (pic *Picture) SetColor(color color.Color) {
	pic.currentColor = color
}

// Shipout returns a completed picture.
func (pic *Picture) Shipout(gfxType string) {
	if pic.output == nil {
		pic.output = NewDebuggingOutputRoutine(tracing.LevelDebug)
	}
	pic.output.Shipout(pic, gfxType)
}

// SetOutputRoutine sets an output routine. Will be used for subsequent
// output of completed images.
func (pic *Picture) SetOutputRoutine(o OutputRoutine) {
	pic.output = o
}

// === Drawable Contour ======================================================

// DrawableContour is an interface for a stroke.
// The central operation on Surfacees is AddContour, the drawing and/or filling
// of a path. Different clients may have a different understanding of what a
// path is and how to store it. Interface DrawableContour tries to be a common
// denomiator for drawing operations on paths.
//
// The 3 parameters for ToNextKnot() are the target point, together with 2
// optional control points for spline curves (quadratic or cubic).
//
type DrawableContour interface {
	IsCycle() bool
	Start() arithm.Pair
	ToNextKnot() (k arithm.Pair, c1 arithm.Pair, c2 arithm.Pair)
}

// === Output Routine ========================================================

// OutputRoutine is an interface for output routines to ship out
// completed images.
type OutputRoutine interface {
	Shipout(pic *Picture, gfxFormat string) bool
}

// debuggingOutputRoutine is a super-simple shipout type, which
// prints a debug representation of a picture to the terminal.
type debuggingOutputRoutine struct {
	level tracing.TraceLevel
}

// NewDebuggingOutputRoutine returns an output routine suited for
// debugging and testing.
//
// Its Shipout method prints a debug representation of a picture to the terminal.
//
// Attention: not yet implemented (TODO) !
func NewDebuggingOutputRoutine(level tracing.TraceLevel) OutputRoutine {
	return debuggingOutputRoutine{level: level}
}

// Shipout prints a debug representation of a picture to the terminal.
//
// Attention: not yet implemented.
//
func (o debuggingOutputRoutine) Shipout(pic *Picture, gfxFormat string) bool {
	G().Errorf("Debugging shipout not yet implemented")
	return false
}
